package dkbta.processing;

import static dkbta.util.Utils.isEmpty;
import static dkbta.util.time.TimeUtils.timeInclMillisAndDate;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import dkbta.ontology.elements.ElementType;
import dkbta.util.Log;

/**
 * A class meant for the timing of actions (as defined in the inner enum Action).
 * 
 * @author Uri Kanonov
 */
public class ActionTimer{
	/**
	 * A detailing of all the valid actions in the system
	 * 
	 * @author Uri Kanonov
	 */
	public enum Action{
		MAPPING,
		INTERPOLATION,
		CONTEXT_GENERATION,
		PATTERN_COMPUTATION,
		REPEATING_PATTERN_COMPUTATION,
		STATISTICAL_ABSTRACTION_COMPUTATION,
		SUBJECT_STATISTICAL_ABSTRACTION_COMPUTATION,
		READING_DATA_FROM_FILE,
		READING_DATA_FROM_DB,
		SENDING_DATA_TO_DB,
		SENDING_DATA_TO_UNITS,
		RECEIVING_DATA,
	}

	/** The action timer's single instance */
	private static ActionTimer _instance = null;

	/** A map from action ID to start time */
	private Map<Integer, Long> _startTimes;

	/** The associated unit id */
	private final int _unitId;

	/**
	 * The next action id to be assigned. Action ids are used to retrieve saved start
	 * times
	 */
	private int _nextActionId;

	/** The stored action times */
	private StringBuilder _actionTimesCSV;

	private ActionTimer(){
		_unitId = -1;
	}

	private ActionTimer(int unitId){
		_unitId = unitId;
		_actionTimesCSV = new StringBuilder(
				"CompUnitId,PatientId,ConceptName,ConceptType,Action,StartTime,EndTime,Duration\n");
		_startTimes = new HashMap<Integer, Long>();
		_nextActionId = 1;
	}

	/**
	 * Initialized an action timer for the unit id
	 * 
	 * @param unitId The unit id
	 * @return The action timer's single instance
	 */
	public static ActionTimer init(int unitId){
		if (_instance != null){
			throw new IllegalStateException("Action Timer is already initialized!");
		}

		try{
			_instance = new ActionTimer(unitId);
		}catch(Throwable e){
			Log.getLogger().severe(
				"Unable to initialize Action Timer, creating null implementation", e);
			_instance = new ActionTimer(){

				@Override
				public synchronized int actionStart(){
					return -1;
				}

				@Override
				public synchronized void actionEnd(int actionId, int patientId,
					String conceptName, ElementType conceptType, Action action){
				}

				@Override
				public void close(){
				}
			};
		}
		return _instance;
	}

	/**
	 * Returns the action timer's single instance
	 * 
	 * @return The action timer's single instance
	 */
	public static ActionTimer getInstance(){
		return _instance;
	}

	/**
	 * Times the action start
	 * 
	 * @return An ID for the action to be passed as the first parameter to actionEnd once
	 *         the action finishes
	 * @see #actionEnd(int, int, String, ElementType, dkbta.processing.ActionTimer.Action)
	 */
	public synchronized int actionStart(){
		int actionId = _nextActionId++;
		_startTimes.put(actionId, System.currentTimeMillis());
		return actionId;
	}

	/**
	 * Times the action end
	 * 
	 * @param actionId ID received from actionStart at the action start
	 * @param patientId The id of the associated patient if one exists, negative number
	 *        otherwise
	 * @param conceptName The name of the associated concept if one exists, "" or null
	 *        otherwise
	 * @param conceptType The type of the associated concept if one exists, null otherwise
	 * @param action The type of action that was timed
	 * @see #actionStart()
	 */
	public synchronized void actionEnd(int actionId, int patientId, String conceptName,
		ElementType conceptType, Action action){
		long endTime = System.currentTimeMillis();
		Long startTime = _startTimes.remove(actionId);
		if (startTime == null){
			throw new IllegalArgumentException(
					"Invalid action-id, hasn't been received through 'operationStart'");
		}
		if (action == null){
			throw new IllegalArgumentException("Invalid action-type, must be non-null");
		}
		_actionTimesCSV.append(_unitId + "," + ((patientId < 0) ? "" : patientId) + ","
				+ (isEmpty(conceptName) ? "" : conceptName) + ","
				+ (conceptType == null ? "" : conceptType) + "," + action + ","
				+ timeInclMillisAndDate(startTime) + "," + timeInclMillisAndDate(endTime)
				+ "," + (endTime - startTime) + "\n");
	}

	public void close(){
		String file = "action-times-" + _unitId + ".csv";
		try{
			Writer w = new FileWriter(file);
			w.write(_actionTimesCSV.toString());
			w.flush();
			w.close();
		}catch(IOException e){
			Log.getLogger().warning("Unable to write action times to: " + file, e);
		}
	}
}
